/**
 * \file errormanager.cpp
 *
 * Asteriskos Compiler 1
 * Written 2010-2011, by Anthony Berlin and Alexander Carlqvist
 *
 * This file is part of ICC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "errormanager.h"

#include <GCB/Common.h>
#include <iostream>

///
/// \brief Write an error or warning to the standard error stream.
/// \param error The error to write.
///
void errormanager::write_error(Error* error)
{
    string errorLine;
    string output;
    string data;
    vector<string>* tokens = NULL;

    if (error == NULL)
        return;

    if (error->m_line == NULL)
    {
        error->m_line = new SourceLine;
        error->m_line->m_data = "";
        error->m_line->m_lineNumber = 0;
        error->m_line->m_transUnit = NULL;
    }
    else if (error->m_line->m_transUnit == NULL)
    {
        error->m_line->m_transUnit = new TranslationUnit;
        error->m_line->m_transUnit->set_path("<unknown file>");
    }

    if (error->m_line->m_lineNumber < 1)
        errorLine = "unknown line";
    else
        errorLine = boost::lexical_cast<string>(error->m_line->m_lineNumber);

    output =
        (string)error->m_line->m_transUnit->get_path() + "(" + errorLine + "): " +
        (error->m_isError ? "error" : "warning") + ": ";

    if (error->m_data != NULL)
        data = *error->m_data;

    switch (error->m_type)
    {
        case ERR_INVALID_TOKEN:
            output += "Invalid token '" + data + "'";
            break;
        case ERR_UNTERMINATED_BRACKET:
            output += "Unterminated '" + data + "' token";
            break;
        case ERR_OPENING_BRACKET_MISSING:
            output += (string)"Expected '" + data[0] + "' before '" + data[1] + "'";
            break;
        case ERR_EXPECTED_A_BEFORE_B:
            tokens = split_error_data_into_tokens(data);
            output += "Expected '" + (*tokens)[0] + "' before '" + (*tokens)[1] + "'";
            break;
        case ERR_EXPECTED:
            output += "Expected " + data;
            break;
        case ERR_INVALID_LVALUE:
            output += "Invalid lvalue '" + data + "'";
            break;
        case ERR_INVALID:
            output += "Invalid " + data;
            break;
        case ERR_DUPLICATE_A:
            output += "Duplicate '" + data + "'";
            break;
        case ERR_A_AND_B_SPECIFIED_TOGETHER:
            tokens = split_error_data_into_tokens(data);
            output += "'" + (*tokens)[0] + "' and '" + (*tokens)[1] + "' specified together";
            break;
        case ERR_A_SPECIFIED_FOR_PARAM_IN_PARAM_DECL_LIST:
            output += "'" + data + "' specified for parameter in parameter-declaration-list";
            break;
        case ERR_A_NOT_DECLARED:
            output += "'" + data + "' was not declared in this scope";
            break;
        case ERR_RETURN_IN_GLOBAL_SCOPE:
            output += "'return' found in global scope";
            break;
        case ERR_ARGS_MISMATCH_FOR_FUNCTION:
            output += "Argument mismatch for function '" + data + "'";
            break;
        case ERR_INVALID_CONVERSION_FROM_A_TO_B:
            tokens = split_error_data_into_tokens(data);
            output += "Invalid conversion from '" + (*tokens)[0] + "' to '" + (*tokens)[1] + "'";
            break;
        case ERR_MAIN_NOT_GLOBAL:
            output += "'main' cannot appear in a local scope";
            break;
        case ERR_UNINIT_CONST_VAR:
            output += "Uninitialized constant variable '" + data + "'";
            break;
        case ERR_REDEF_CONST_VAR:
            output += "Redefinition of constant variable '" + data + "'";
            break;
        case ERR_ASSIGNMENT_OF_NON_LOCAL_VAR_IN_READONLY_STRUCTURE:
            output += "Assignment of non-local variable '" + data + "' in readonly structure";
            break;
        case ERR_MAIN_HAS_PARAMS:
            output += "'main' cannot be declared with parameters";
            break;
        case ERR_NO_RETURN_IN_FUNC:
            output += "No return-statement found when control reaches the end of the function body";
            break;
        case ERR_NON_VOID_FUNC_OF_TYPE_A_MISSING_RETURN_EXPR:
            output += "Non-void function of type '" + data + "' missing return expression";
            break;
        case ERR_FUNCTION_A_WAS_DECLARED_IN_MODULE_B:
            tokens = split_error_data_into_tokens(data);
            output += "Function '" + (*tokens)[0] + "' was declared in module '" + (*tokens)[1] + "'";
            break;
        case ERR_IDENT_A_MUST_BE_MODULE:
            output += "Identifier '" + data + "' must be a module";
            break;
        case ERR_CANNOT_DEFINE_EXTERNAL_FUNCTION_A:
            output += "Cannot define an external function '" + data + "'";
            break;
        case ERR_DUPLICATE_DEFINITION_OF_FUNCTION_A:
            output += "Duplicate definition of function '" + data + "'";
            break;
        case ERR_STRAY_A_STATEMENT:
            output += "Stray " + data + "-statement";
            break;
        case WARNING_UNKNOWN_ESCAPE_SEQUENCE:
            output += "Unknown escape sequence";
            break;
        case WARNING_ELSE_WITHOUT_PREVIOUS_IF:
            output += "'else' without a previous 'if'";
            break;
        case WARNING_A_MAY_SHADOW_DECL_OF_VAR:
            output += "'" + data + "' may shadow declaration of a variable with the same name";
            break;
        case WARNING_POSSIBLE_LOSS_OF_DATA_WHEN_CONVERTING_TO_A:
            output += "Possible loss of data when converting expression to '" + data + "'";
            break;
        case WARNING_MAIN_IMPORTED_FROM_MODULE_A:
            output += "A symbol named 'main' was imported from module '" + data + "'";
            break;
        case WARNING_COULD_NOT_LOCATE_MODULE_A:
            output += "Could not locate module '" + data + "'";
            break;
        case WARNING_SKIPPING_RECURSIVE_IMPORT_OF_MODULE_A:
            output += "Skipping recursive import of module '" + data + "'";
            break;
        default:
            output += "Unknown ";

            if (error->m_isError)
                output += "error";
            else
                output += "warning";

            break;
    }

    std::cerr << output << ".\n";
}

///
/// \brief Split error data into tokens, and put them in a \c vector.
/// \param data The data string that will be split into tokens.
/// \return \c vector of strings containing the tokens.
///
vector<string>* errormanager::split_error_data_into_tokens(string& data)
{
    vector<string>* tokens = new (GC) vector<string>;
    split(*tokens, data, boost::is_any_of("#"), boost::token_compress_on);
    return tokens;
}
